﻿using RandomKeyGenerator;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AlgoritmoConstrutivo.entidades
{
    [Serializable]
    public class Grafo : IComparable
    {
        private string nome;
        private Decimal mq;
        private bool ehPonderada;

        public bool EhPonderada
        {
            get { return ehPonderada; }
            set { ehPonderada = value; }
        }
   
        //faz o link entre o MDG e o cromossomo em uma populaçao específica.
        private int idOriginalCromossomo = -1;

        private int qtdeClustersUnitarios = -1;

        public int QtdeClustersUnitarios
        {
            get { return qtdeClustersUnitarios; }
            set { qtdeClustersUnitarios = value; }
        }


        private Cromossomo cromossomo;

        private Decimal tempoObterMq;
        private double mutante;
        private double elite;
        private bool ponderado;

        private List<int> ordemAtribuicaoCabecasChave = new List<int>();

        public List<int> OrdemAtribuicaoCabecasChave
        {
            get { return ordemAtribuicaoCabecasChave; }
            set { ordemAtribuicaoCabecasChave = value; }
        }

       
        private Vizinhos nos;
        private List<Cluster> clusters = new List<Cluster>();

    #region construtores
        public Grafo()    
    {
        this.nos = new Vizinhos();
    }

    public Grafo(string nome)
        : this()
    {
        this.nome = nome;

    }

    public Grafo(Vizinhos NoSet)
    {
        if (NoSet == null)
            this.nos = new Vizinhos();
        else
            this.nos = NoSet;
    }
        #endregion

        
    #region manipulacao de MQ

    public void CalculaMQSomenteSoma()
    {
        foreach (Cluster cl in this.clusters)
        {
            if (cl.RetornarNumNosCluster() > 0)
                this.mq += cl.Mf;
        }

        Math.Round(this.mq, 5);
    }
        
    public void CalculaMQ()
    {
        this.ZerarArestasInternasExternas();


        if (!ponderado)
        {

            foreach (Cluster cl in this.clusters)
            {
                if (cl.RetornarNumNosCluster() > 0)
                    this.mq += cl.CalculaMF(this);
            }
        }
        else
        {
            foreach (Cluster cl in this.clusters)
            {
                if (cl.RetornarNumNosCluster() > 0)
                    this.mq += cl.CalculaMFGrafoPonderado(this);
            }      
        }

    }

    public void ZerarArestasInternasExternas()
    {
        this.mq = 0;
        foreach (Cluster cl in this.clusters)
        {
            cl.Internas = 0;
            cl.Externas = 0;
            cl.Mf = 0;
        }
    }

    public void LimpaGrafo()
    {
        this.mq = 0;
        this.clusters = new List<Cluster>();
        this.idOriginalCromossomo = -1;
        this.OrdemAtribuicaoCabecasChave = new List<int>();
        

        foreach (No n in this.nos)
        {
            n.LimpaNos();
        }

        //codigo novo de limpeza.


        foreach (Gene g in this.cromossomo.Chaves)
        {
            g.Indice = -1;
            g.KeyCriterio = -1;
            g.KeyOrdenacao = -1;           
        }

        this.cromossomo.Value = -1;

    }

    #endregion



    #region metodos de manipulacao de no
    public void AddNo(No no) //so se o no nao existir.
    {
        if (!this.Contains(no.Id))
            this.nos.Add(no);
    }


    public List<No> OrdenaNosPorGrau()
    {

        return this.nos.OrderBy(i => i.Grau).ToList();

    }

   

    public void CalculaGrauNos()
    {
        int count = 0;

        for (int i = 0; i < this.RetornarNumNos(); i++)
        {
            count = 0;

            No n = this.RetornaNo(i);

            if (n.NumAtualConexoesVizinhos != -1)
                continue;

            List<No> vizinhos = n.ArestasQueSaem.ToList<No>();

            for (int j = 0; j < vizinhos.Count; j++)
            {
                for (int k = 0; k < vizinhos.Count; k++)
                {
                    if (k == j)
                        continue;

                    if (vizinhos[j].ArestasQueSaem.Contains(vizinhos[k]))
                        count++;
                }
            }

            n.NumAtualConexoesVizinhos = count;


        }
    }
         
        


    public int RetornarNumNos()
    {
        return this.nos.Count;
    }


    public No RetornaNo(int id)
    {
 
        return this.nos[id];

    }

    public List<No> RetornaNoAsList()
    {
        return this.nos.ToList<No>();

    }

    public bool Ponderado
    {
        get { return ponderado; }
        set { ponderado = value; }
    }


    public void LimpaNos()
    {
        this.nos = null;

    }

    #endregion


    #region Metodos de Cluster

    private void AtualizaClusterDeUmNo(Cluster cluNovo, No no)
    {
        if (no.Cluster == null)
            throw new ArgumentNullException("O no " + no + " a ter o cluster atualizado não possui nenhum cluster");

        Cluster clAntigo = no.Cluster;

        clAntigo.RemoverNo(no.Id);

        no.ConfiguraCluster(cluNovo,this);

        if (no.Cluster == null)
            throw new ArgumentNullException("O no " + no + " a ter o cluster atualizado não possui nenhum cluster");

    }

    public void RemoverClustersVazios()
    {
        for (int i=0; i< this.clusters.Count;i++)
        {
            if (this.clusters[i].RetornarNumNosCluster() == 0)
                this.clusters.Remove(clusters[i]);
        }
    }


    /// <summary>
    /// Inicializa uma quantidade X de clusters e atualiza a lista de clusters do Grafo.
    /// </summary>
    /// <param name="qtdeInicialCluster">Quantidade inicial de Clusters</param>
    /// <param name="clusterJáCriados">Listagem dos Clusters ja existentes</param>
    /// <param name="software">O grafo que representa um software</param>
    public void ConfigurarClusters(int qtdeInicialCluster)
    {
        for (int i = 0; i < qtdeInicialCluster; i++)
        {
            Cluster cl = new Cluster(true);
            this.AdicionarCluster(cl);
        }
    }

    public void ConfigurarClusterDeUmNo(Cluster clu, No no)
    {
        if (no.Cluster != null)
            this.AtualizaClusterDeUmNo(clu, no);
        else
            no.ConfiguraCluster(clu,this);

        this.AdicionarCluster(clu);


    }


    public void AdicionarCluster(Cluster cl)
    {
        if (!this.clusters.Contains(cl))
            this.clusters.Add(cl);

    }

    public Cluster RetornarCluster(int id)
    {
        if (id >= this.clusters.Count)
            throw new ArgumentException("Somente existem " + this.clusters.Count + " e tentou-se acessar o cluster de id " + id);

        return this.clusters[id];
    }

    public int RetornarNumClusters()
    {
        int count = 0;
        for (int i = 0; i < this.clusters.Count; i++)
        {
            if (this.clusters[i].RetornarNumNosCluster() != 0)
                count++;
        }

        if (count == 0)
            return this.clusters.Count;
        else
            return count;

        //return this.clusters.Count;
    }

    public void InicializarClusters()
    {
        if (this.clusters != null)
            throw new ArgumentException("A lista de cluster já foi iniciada");
        this.clusters = new List<Cluster>();
    }

    public int RetornaNumeroClustersUnitarios()
    {
        int qtde = 0;


        foreach (Cluster cl in this.clusters)
        {
            if (cl.RetornarNumNosCluster() == 1)
                qtde++;
        }

        return qtde;
    }

    public void OrdenarClusters()
    {
        if (this.clusters == null)
            throw new NullReferenceException("Tentou-se ordenar uma lista de clusters não inicializada");

        if (this.clusters.Count == 0)
            throw new Exception("Tentou-se ordenar uma lista de clusters vazia");

        this.clusters.Sort();
    }

    public Cluster RetornarUltimoCluster()
    {
        this.OrdenarClusters();

        return this.clusters.Last();
    }

    #endregion



    #region manipulacao de arestas


    public int NumeroDependencias()
    {
        int count = 0;
        foreach (No n in this.nos)
        {
            count += n.ArestasQueSaem.Count;
        }

        return count;
    }


    public void AdicionaArestaDirecionadaPonderada(int de, int para, int peso)
    {
        No origem = this.nos.FindByValue(de);

        if (origem == null)
            throw new ArgumentException("O no origem de identificação " + de + " não foi encontrado na lista");

        if (!this.Contains(para))
            throw new ArgumentException("O no destino de identificação " + para + " não foi encontrado na lista");

        No destino = this.nos.FindByValue(para);

        origem.ArestasQueSaem.Add(destino);
        origem.ArestasQueSaem.Pesos.Add(peso);
            
       
        destino.ArestasQueEntram.Add(origem);
        destino.ArestasQueEntram.Pesos.Add(peso);

    }

    public void AdicionaArestaDirecionada(int de, int para)
    {
        No origem = this.nos.FindByValue(de);

        if (origem == null)
            throw new ArgumentException("O no origem de identificação " + de + " não foi encontrado na lista");
 
        if(!this.Contains(para))
            throw new ArgumentException("O no destino de identificação " + para + " não foi encontrado na lista");

        No destino = this.nos.FindByValue(para); 

        origem.ArestasQueSaem.Add(destino);

        destino.ArestasQueEntram.Add(origem);
               
    }

    public int NumArestasExternasComNo(Cluster cl, No no)
    {
        int numArestasComNo = 0;

        for (int i = 0; i < cl.RetornarNumNosCluster(); i++) //PARA CADA NO DENTRO DE UM CLUSTER
        {
            if (this.nos[cl.RetornarNo(i)].ArestasQueSaem.Contains(no))// o no sem cluster faz parte da lista de vizinho do 
                numArestasComNo++;
        }

        return numArestasComNo;
    }

    #endregion


    #region setter e getters
    public int Count
    {
        get { return nos.Count; }
    }

    public double Mutante
    {
        get { return mutante; }
        set { mutante = value; }
    }

    public Cromossomo Cromossomo
    {
        get { return cromossomo; }
        set { cromossomo = value; }
    }

    public Decimal Mq
    {
        get { return mq; }
        set { mq = value; }
    }

    public Decimal TempoObterMq
    {
        get { return tempoObterMq; }
        set { tempoObterMq = value; }
    }

    public string Nome
    {
        get { return nome; }
        set { nome = value; }
    }

    public double Elite
    {
        get { return elite; }
        set { elite = value; }
    }

    public int IdOriginalCromossomo
    {
        get { return idOriginalCromossomo; }
        set { idOriginalCromossomo = value; }
    }

    #endregion

    #region  outros


    public void Validate()
    {
        bool erro = false;

        var hasDupes = this.nos.GroupBy(x => new { x.Id })
       .Where(x => x.Skip(1).Any()).Any();

        var hasDupesClusters = this.clusters.GroupBy(x => new { x.Id })
       .Where(x => x.Skip(1).Any()).Any();



        //existem nós duplicados na lista de nós do grafo?
        if (hasDupes == true)
        {
            File.WriteAllText(@"C:/Mestrado/Erro.txt", "Existem nós duplicados na lista de nó do cluster " + this.nome);
            erro = true;
        }

        //existem Clusters duplicados na lista de nós do grafo?
        if (hasDupesClusters == true)
        {
            File.WriteAllText(@"C:/Mestrado/Erro.txt", "Existem Clusters duplicados na lista de nó do cluster " + this.nome);
            erro = true;
        }

       
        //Todos os nós possuem cluster?
        foreach (No n in this.nos)
        {
            if (n.Cluster == null)
            {
                File.WriteAllText(@"C:/Mestrado/Erro.txt", "O no de nome" + n.Nome + "não possui um cluster associado.Este nó possui "
                    + n.ArestasQueEntram.Count + " Arestas que entram e " + n.ArestasQueSaem.Count);
                erro = true;
            }

            else
            {

                foreach (Cluster cl in this.clusters)
                {
                    if (n.Cluster == cl)
                    {
                        //O nó que possui cluster, está corretamente inserido na lista de nós de seu Cluster?
                        if (!cl.PossuiNo(n.Id))
                        {
                            File.WriteAllText(@"C:/Mestrado/Erro.txt", "O no de nome" + n.Nome + "aparentemente possui Cluster de id " + n.Cluster.Id
                             + " mas não está na como um inteiro na lista de nós de seu Cluster");
                            erro = true;
                        }

                    }
                    else
                    {
                        //O nó que possui cluster, está inserido erroneamente na lista de nós de outro Cluster?
                        if (cl.PossuiNo(n.Id))
                        {
                            File.WriteAllText(@"C:/Mestrado/Erro.txt", "O no de nome" + n.Nome + " possui Cluster de id " + n.Cluster.Id
                            + " mas também consta na lista do Cluster de id "+cl.Id);
                            erro = true;
                        }
                    }
                }
            }

        }

        if (erro)
            throw new Exception("Existem erros na validação do software " + this.nome + ". Verificar o arquivo de LOG");
    }



    public bool Contains(int value)
    {
        return this.nos.FindByValue(value) != null;
    }

    public int CompareTo(object obj)
    {
        Grafo gr = obj as Grafo;

        if (gr.mq > this.mq)
            return 1;

        else if (gr.mq == this.mq)
            return 0;
        else
            return -1;
    }
        
    public override string ToString()
    {
        return (" O  grafo de nome " + this.nome + " foi gerado com " + this.clusters.Count + " clusters e possui um MQ de " + this.mq.ToString());
    }

    #endregion



    public double Seed { get; set; }
    }
}
